feat: add admin dashboard with account management and service health#24
Merged
Conversation
- Server /auth/user now returns role for client navigation affordance - Web auth types map role through get-user, login, register - Add admin web API modules: list, detail, update, reset password, delete, restore - Add admin query keys with mutation invalidation metadata - Server admin authorization remains source of truth
- Add AdminLayout with dashboard-like chrome, shield mark, back-to-app link - Separate admin route group (_protected/admin) with role guard in beforeLoad - Add accounts page with status filters and user table (desktop) / cards (mobile) - Add user edit dialog: name, role, password reset, delete, restore - Role selector uses DropdownMenu pattern matching settings page - Disable admin-target mutations (last-admin safety, server guard authoritative) - Add admin entry to UserMenu for admin role only - Add table outer border and full i18n namespace for admin copy
…e counts Phase 4b - service connections: - Add GET /admin/health/connections admin route - Add browser.service checkHealth() probe with 5s timeout - Add admin-health.service aggregating connection checks (camoufox first) - Break import cycle via lazy import of browser.service - Add admin connections panel with 30s auto-refresh - Calm status colors: success/warning/danger, no urgency framing - Add connections route and nav item Phase 4c - per-user article counts: - Add countArticlesByUser() repository method (groupBy aggregation) - Enrich admin user list response with articleCount - Add articles column to admin user table (desktop) and mobile card - Use tabular-nums for stable numeric layout Verification: - pnpm --filter @loreo/server typecheck passed - pnpm --filter @loreo/web typecheck passed - pnpm --filter @loreo/server exec vitest --run admin files passed: 24 tests
Login/register routes used selectUsersSchema which omits role. The web client cached the user via setQueryData with role undefined, and staleTime: Infinity on useGetUser prevented refetch. This hid the Admin menu entry on the main page for admin users. Switch login/register response schemas to currentUserSchema and include role in both handlers.
Locks the contract added in the previous role-exposure fix. Future regressions that strip role from login/register responses will now fail the suite instead of silently breaking admin navigation on the web.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an admin-only dashboard for account management and operational visibility. New
adminroute group on the server and a dedicated dashboard-like layout on the web, kept separate from the reading-product shell.Server authorization is the source of truth; client role is only a navigation affordance.
What's included
Server — admin route group (
/admin/*)GET /admin/users— list withlimit,offset,status=active|deleted|all, plus per-userarticleCountGET /admin/users/:id— detailPATCH /admin/users/:id— updatenameandrolePOST /admin/users/:id/reset-password— reset passwordDELETE /admin/users/:id— soft deletePOST /admin/users/:id/restore— restore soft-deleted userGET /admin/health/connections— camoufox browser probe with 5s timeout, returnsok/degraded/downEvery route runs
currentUserthenadminUser. Demo mode blocks mutations.Server — auth
/auth/usernow returnsroleWeb — admin dashboard
AdminLayout: separate chrome (no main navbar/bottom nav), shield mark, title/subtitle, back-to-app link, user menu, section navbeforeLoad(server remains authority)DropdownMenurole selector matching settings patternen.jsonWeb — navigation
role === 'admin'Guards and invariants
Verification
pnpm --filter @loreo/server typecheckpnpm --filter @loreo/web typecheckpnpm --filter @loreo/server testpnpm --filter @loreo/web testpnpm lint(oxlint + oxfmt)Manual UI checks (admin/non-admin sessions, desktop/tablet/mobile, keyboard paths, light/dark/sepia) still pending.